﻿// --------------------------------
// <copyright file="CalculationModel.cs" company="TRONSoft">
//     Copyright (c) TRONSoft.
// </copyright>
// <license>
//   This source code is subject to terms and conditions of the Microsoft
//   Public License. A copy of the license can be found in the License.html
//   file at the root of this distribution. If you cannot locate the
//   Microsoft Public License, please send an email to dlr@microsoft.com.
//   By using this source code in any fashion, you are agreeing to be bound 
//   by the terms of the Microsoft Public License. You must not remove this
//   notice, or any other, from this software.
// </license>
// <author>Tron</author>
// ---------------------------------

using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Reflection;
using Microsoft.VisualBasic;
using Microsoft.VisualBasic.FileIO;
using Tronsoft.Actucal.Tables;

namespace Tronsoft.Actucal.CalculationEngineCreator
{
    public class CalculationModel : ICalculationEvaluator
    {
        #region Constants

        public readonly string CALCULATION_ENGINE_CLASSNAME = "CalculationEngine";
        public readonly string CALCULATION_ENGINE_NAMESPACE = "Actucal.Calculation";
        public readonly string FULLY_QUALIFIED_CALCULATION_ENGINE_CLASSNAME = "Actucal.Calculation.CalculationEngine";

        #endregion

        #region Private/Protected Fields

        private static readonly object syncObject = new object();
        private readonly CalculationParameterCollection calculationParameters;
        private readonly CalculationCollection calculations;
        private readonly ConstantCollection constants;
        // private Assembly compiledAssembly;
        private readonly IncludeCollection includes = new IncludeCollection();
        private readonly MethodCollection methods = new MethodCollection();
        private readonly ReferencedAssemblyCollection referencedAssemblies = new ReferencedAssemblyCollection();
        private object calculationClass;

        #endregion

        #region ctors

        public CalculationModel()
            : this(new CalculationParameterCollection(), new CalculationCollection(), new ConstantCollection())
        {
        }

        public CalculationModel(CalculationParameterCollection calculationParameters)
            : this(calculationParameters, new CalculationCollection(), new ConstantCollection())
        {
        }

        public CalculationModel(CalculationCollection calculations)
            : this(new CalculationParameterCollection(), calculations, new ConstantCollection())
        {
        }

        public CalculationModel(CalculationParameterCollection calculationParameters, CalculationCollection calculations)
            : this(calculationParameters, calculations, new ConstantCollection())
        {
        }

        public CalculationModel(ConstantCollection constants)
            : this(new CalculationParameterCollection(), new CalculationCollection(), constants)
        {
        }

        public CalculationModel(CalculationParameterCollection calculationParameters, ConstantCollection constants)
            : this(calculationParameters, new CalculationCollection(), constants)
        {
        }

        public CalculationModel(CalculationParameterCollection calculationParameters,
                                CalculationCollection calculations, ConstantCollection constants)
        {
            if (calculationParameters == null) throw new ArgumentNullException("calculationParameters");
            if (calculations == null) throw new ArgumentNullException("calculations");
            if (constants == null) throw new ArgumentNullException("constants");

            this.calculations = calculations;
            this.constants = constants;
            this.calculationParameters = calculationParameters;

            referencedAssemblies.Add("System.dll");
            referencedAssemblies.Add("System.Data.dll");
            referencedAssemblies.Add("System.Drawing.dll");
            referencedAssemblies.Add("System.Xml.dll");
            referencedAssemblies.Add("System.Windows.Forms.dll");
            referencedAssemblies.Add("System.Configuration.dll");
            referencedAssemblies.Add(typeof (CalculationModel).Assembly.Location);
            referencedAssemblies.Add(typeof(TableManager).Assembly.Location);

            includes.Add("System");
            includes.Add("System.IO");
            includes.Add("System.Text");
            includes.Add("System.Text.RegularExpressions");
            includes.Add("System.Collections");
            includes.Add("System.Collections.Generic");
            includes.Add("System.Data");
            includes.Add("System.Globalization");
            includes.Add("System.Math");
            includes.Add("System.Reflection");
            includes.Add(typeof(CalculationModel).Namespace);
            includes.Add(typeof(TableManager).Namespace);
        }

        #endregion

        public ReferencedAssemblyCollection ReferencedAssemblies
        {
            get { return referencedAssemblies; }
        }

        public string Source
        {
            get
            {
                SourceCodeGenerator generator = new SourceCodeGenerator(this);
                return generator.GetSourceCode();
            }
        }

        public CalculationParameterCollection CalculationParameters
        {
            get { return calculationParameters; }
        }

        public CalculationCollection Calculations
        {
            get { return calculations; }
        }

        public ConstantCollection Constants
        {
            get { return constants; }
        }

        public MethodCollection Methods
        {
            get { return methods; }
        }

        public IncludeCollection Includes
        {
            get { return includes; }
        }

        #region ICalculationEvaluator Members

        public double Eval(string calculationName)
        {
            Predicate<Calculation> namesAreEqual = calculation => calculation.Name == calculationName;
            if (!calculations.Exists(namesAreEqual))
            {
                throw new CalculationException("Unknown calculation.");
            }

            return Eval(calculations.Find(namesAreEqual));
        }

        public double Eval(Calculation calculation)
        {
            if (!calculations.Contains(calculation))
            {
                throw new CalculationException("Unknown calculation.");
            }

            if (null == calculationClass)
            {
                lock (syncObject)
                {
                    if (null == calculationClass)
                    {
                        CompileEngine engine = new CompileEngine(this);
                        calculationClass = engine.CreateInstanceCalculationClass();
                    }
                }
            }

            return (double)calculationClass.GetType().InvokeMember(calculation.Name,
                                                                   BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty,
                                                                   null,
                                                                   calculationClass,
                                                                   new object[] { },
                                                                   CultureInfo.InvariantCulture);
        }
        #endregion

        #region Helper Methods

        public void SaveAssembly(string path)
        {
            if (path == null) throw new ArgumentNullException("path");

            File.Delete(path);

            CompileEngine engine = new CompileEngine(this);
            engine.CreateAssembly(path);
        }

        public CalculationParameter<T> AddCalculationParameter<T>(string name) 
        {
            if (name == null) throw new ArgumentNullException("name");

            CalculationParameter<T> parameter = new CalculationParameter<T>(name);
            calculationParameters.Add(parameter);

            return parameter;
        }

        public CalculationParameter<T> AddCalculationParameter<T>(string name, T value) 
        {
            if (name == null) throw new ArgumentNullException("name");

            CalculationParameter<T> parameter = new CalculationParameter<T>(name, value);
            calculationParameters.Add(parameter);

            return parameter;
        }

        public Calculation AddCalculation(string name, string value)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (value == null) throw new ArgumentNullException("value");

            Formula formula = new Formula(name, value);
            Calculation calculation = new Calculation(formula);

            calculations.Add(calculation);

            return calculation;
        }

        public Calculation AddCalculation(Formula formula)
        {
            if (formula == null) throw new ArgumentNullException("formula");

            Calculation calculation = new Calculation(formula);
            calculations.Add(calculation);

            return calculation;
        }

        public NameValueCombination<T> AddConstant<T>(string name) where T : IConvertible
        {
            if (name == null) throw new ArgumentNullException("name");

            NameValueCombination<T> constant = new NameValueCombination<T>(name);
            constants.Add(constant);

            return constant;
        }

        public NameValueCombination<T> AddConstant<T>(string name, T value) where T : IConvertible
        {
            if (name == null) throw new ArgumentNullException("name");

            NameValueCombination<T> constant = new NameValueCombination<T>(name, value);
            constants.Add(constant);

            return constant;
        }

        public void AddConstant(INameValueCombination constant)
        {
            if (constant == null) throw new ArgumentNullException("constant");

            constants.Add(constant);
        }

        public MethodDefinition AddMethod(string name, MethodSignature signature, string methodDeclaration)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (signature == null) throw new ArgumentNullException("signature");
            if (methodDeclaration == null) throw new ArgumentNullException("methodDeclaration");

            MethodDefinition method = new MethodDefinition(name, signature, methodDeclaration);
            methods.Add(method);

            return method;
        }

        public void AddReference(string assemblyName)
        {
            if (assemblyName == null) throw new ArgumentNullException("assemblyName");

            if (!referencedAssemblies.Exists(delegate(string name) { return (name == assemblyName); }))
            {
                referencedAssemblies.Add(assemblyName);
            }
        }

        public void AddInclude(string includeNamespace)
        {
            if (includeNamespace == null) throw new ArgumentNullException("includeNamespace");

            if (!includes.Exists(delegate(string include) { return (include == includeNamespace); }))
            {
                Includes.Add(includeNamespace);
            }
        }

        #endregion
    }
}